#include "kernel.h"

unsigned char * hwaddress;

dword pcnet_base;
unsigned long pcnet_dev, pcnet_bus;
unsigned int pcnet_chip_version;
unsigned long pcnet_entry_point;

/*
 *
 * PCnet32 access functions
 *
 */

/*
 *
 * lance_poll() now can use multiple Rx buffers to prevent packet loss. Set
 *  Set LANCE_LOG_RX_BUFFERS to 0..7 for 1, 2, 4, 8, 16, 32, 64 or 128 Rx
 *  buffers. Usually 4 (=16 Rx buffers) is a good value. (Andreas Neuhaus)
 *  Decreased to 2 (=4 Rx buffers) (Ken Yap, 20010305)
 *
 */


/*
 *
 * The PCNET32 32-Bit initialization block, described in databook
 *
 */
struct pcnet32_init_block {
	u16	mode;
	u16	tlen_rlen;
	u8	phys_addr[ETH_ALEN];
	u16	reserved;
	u32	filter[2];
	/*
         *
         * Receive and transmit ring base, along with extra bits
         *
         */
	u32	rx_ring;
	u32	tx_ring;
};

struct pcnet32_access {
	u16	(*read_csr) (unsigned long, int);
	void	(*write_csr) (unsigned long, int, u16);
	u16	(*read_bcr) (unsigned long, int);
	void	(*write_bcr) (unsigned long, int, u16);
	u16	(*read_rap) (unsigned long);
	void	(*write_rap) (unsigned long, u16);
	void	(*reset) (unsigned long);
};

/*
 *
 * From AMD Datasheets:
 *
 * RAP Register
 *
 * The RAP (Register Address Pointer) register is used to gain access to CSR 
 *  and BCR registers on board PCnet-32 controller. ...
 *
 * ...
 * 
 * As an example of RAP use, consider a read access to CSR4. In order to
 *  access this register, it is first necessary to load 0004 into RAP
 *  by performing a write access to the RAP offset of 12h (12h when wio mode
 *  has been selected, 14h when DWIO mode has been selected). The data for the
 *  RAP write would be 0004. Then a second access is performed on the PCnet-32 controller
 *  , this time to the RCP offset of 10h (for either WIO or DWIO mode). The RDP
 *  access is a read access, and since RAP has just been loaded with 0004, 
 *  the RDP read will yield the contents of CSR4
 *
 */
static u16 pcnet32_wio_read_csr(unsigned long addr, int index)
{
	outw(addr + PCNET32_WIO_RAP, index);
	return inw(addr + PCNET32_WIO_RDP);
}

static void pcnet32_wio_write_csr(unsigned long addr, int index, u16 val)
{
	outw(addr + PCNET32_WIO_RAP, index);
	outw(addr + PCNET32_WIO_RDP, val);
}

static u16 pcnet32_wio_read_bcr(unsigned long addr, int index)
{
	outw(addr + PCNET32_WIO_RAP, index);
	return inw(addr + PCNET32_WIO_BDP);
}

static void pcnet32_wio_write_bcr(unsigned long addr, int index, u16 val)
{
	outw(addr + PCNET32_WIO_RAP, index);
	outw(addr + PCNET32_WIO_BDP, val);
}

static u16 pcnet32_wio_read_rap(unsigned long addr)
{
	return inw(addr + PCNET32_WIO_RAP);
}

static void pcnet32_wio_write_rap(unsigned long addr, u16 val)
{
	outw(addr + PCNET32_WIO_RAP, val);
}

static void pcnet32_wio_reset(unsigned long addr)
{
	inw(addr + PCNET32_WIO_RESET);
}

/*
 *
 * This is to just check whether it is possible to do IO
 *  to the IO Address. Data is written to the default PCnet
 *  -32 RAP offset after IO address and read back. If
 *  data written == data read, then PCnet-32 is functioning well
 *
 */
static int pcnet32_wio_check(unsigned long addr)
{
	outw(addr + PCNET32_WIO_RAP, 88);
	return (inw(addr + PCNET32_WIO_RAP) == 88);
}

static struct pcnet32_access pcnet32_wio = {
	.read_csr = pcnet32_wio_read_csr,
	.write_csr = pcnet32_wio_write_csr,
	.read_bcr = pcnet32_wio_read_bcr,
	.write_bcr = pcnet32_wio_write_bcr,
	.read_rap = pcnet32_wio_read_rap,
	.write_rap = pcnet32_wio_write_rap,
	.reset = pcnet32_wio_reset
};

static u16 pcnet32_dwio_read_csr(unsigned long addr, int index)
{
	outl(addr + PCNET32_DWIO_RAP, index);
	return (inl(addr + PCNET32_DWIO_RDP) & 0xffff);
}

static void pcnet32_dwio_write_csr(unsigned long addr, int index, u16 val)
{
	outl(addr + PCNET32_DWIO_RAP, index);
	outl(addr + PCNET32_DWIO_RDP, val);
}

static u16 pcnet32_dwio_read_bcr(unsigned long addr, int index)
{
	outl(addr + PCNET32_DWIO_RAP, index);
	return (inl(addr + PCNET32_DWIO_BDP) & 0xffff);
}

static void pcnet32_dwio_write_bcr(unsigned long addr, int index, u16 val)
{
	outl(addr + PCNET32_DWIO_RAP, index);
	outl(addr + PCNET32_DWIO_BDP, val);
}

static u16 pcnet32_dwio_read_rap(unsigned long addr)
{
	return (inl(addr + PCNET32_DWIO_RAP) & 0xffff);
}

static void pcnet32_dwio_write_rap(unsigned long addr, u16 val)
{
	outl(addr + PCNET32_DWIO_RAP, val);
}

static void pcnet32_dwio_reset(unsigned long addr)
{
	inl(addr + PCNET32_DWIO_RESET);
}

static int pcnet32_dwio_check(unsigned long addr)
{
	outl(addr + PCNET32_DWIO_RAP, 88);
	return ((inl(addr + PCNET32_DWIO_RAP) & 0xffff) == 88);
}

static struct pcnet32_access pcnet32_dwio = {
	.read_csr = pcnet32_dwio_read_csr,
	.write_csr = pcnet32_dwio_write_csr,
	.read_bcr = pcnet32_dwio_read_bcr,
	.write_bcr = pcnet32_dwio_write_bcr,
	.read_rap = pcnet32_dwio_read_rap,
	.write_rap = pcnet32_dwio_write_rap,
	.reset = pcnet32_dwio_reset
};

int pcnet32_probe1(unsigned long ioaddr)
{
	const struct pcnet32_access *a;
        a = 0;
	struct pcnet_priv *lp = &pcnet32_private;
	unsigned short l;
	int  chip_version;
	int i, bit_mod = -1; 

	/*
         *
         * a. Reset the chip and set bit modifier
         *
         */
	print("PCnet32_probe1->reset chip\n");

        /*
         *
         * 4d == 100b, ie., 3rd bit (counting from 0) is set. CSR0's bit 2 is the stop bit.
         *  It is set by writing a 1 or by H_RESET or S_RESET. STOP is cleared
         *  by seting either STRT or INIT. Hence read from CSR0 and comparison
         *  with 4, means checking whether the controller has been stopped or not
         *
         */

        /*
         *
         * b. set Bit modifier for operation
         *
         * Bit modifier of 0 means 16 bit mode
         * Bit modifier of 1 means 32 bit mode
         *
         */
	if (pcnet32_wio_read_csr(ioaddr, 0) == 4 && pcnet32_wio_check(ioaddr)) {
	        pcnet32_wio_reset(ioaddr);
		a = (unsigned long *) &pcnet32_wio;
		bit_mod = 0;
	} else {
		pcnet32_dwio_reset(ioaddr);
		if (pcnet32_dwio_read_csr(ioaddr, 0) == 4 && pcnet32_dwio_check(ioaddr)) {
			a = (unsigned long *) &pcnet32_dwio;
			bit_mod = 1;
		} else {
			bit_mod = 2;
			goto err_release_region;
		}
	}
	print("PCnet32_probe1->set bit modifier: ");
	if (bit_mod == 0) print("16\n");
	else if (bit_mod == 1) print("32\n");
	else print("bid_mod\n");
	
	/*
         *
         * c. Identify chip version
         *
         * CSR 88 has lower 16 bits of the chip version and 
         *  CSR 89 has upper 16 bits of chip version
         *
         */
	chip_version =  (unsigned int) pcnet32_wio_read_csr(ioaddr, 88) | (pcnet32_wio_read_csr(ioaddr, 89) << 16);


        /*
         *
         * Check if chip version is unsupported or not
         *
         */
	if ((chip_version & 0xfff) != 0x003) {
		print("PCnet32_probe1->chip version: Unsupported chip version \n");
		goto err_release_region;
	}
	
	/*
         *
         * Chip version is shifted right by 12 bits and converted to a 4 byte integer
         *  If the resultant number is equal to 2621, then the chip version is 
         *  AMD PCnet/PCI II 79c970A
         *
         */
	chip_version = (chip_version >> 12) & 0xffff;

	if (chip_version == 0x2621) {
		 print("PCnet32_probe1->chip version: PCnet/PCI II 79C970A\n");
	}

	/*
         *
         * switch pcnet32 to 32bit mode
         *
	 * pcnet32_wio_write_bcr(ioaddr, 20, 2);
         *
         */

	/*
         *
         * d. Disable transmit and receiver
         *
         * Bit 1 and 0  of Mode register (CSR 15) are for disabling 
         *  transmit and receiver respectively
         *
         *  
         * If Logical Address Filter is set to 0, then
         *  all incoming logical addresses except broadcast
         *  are rejected
         *
         */
	lp->init_block.mode = 0x3;
	lp->init_block.filter[0] = lp->init_block.filter[1] = 0x0;

	/*
         *
         * e. set init block rx and tx ring addresses
         *     and write the address of init block to CSR 1 and 2 
         *     (LSB and MSB respectively)
         *
         */

	lp->init_block.rx_ring = ((unsigned int)virt_to_bus(&lp->rx_ring) & 0xffffff) | RX_RING_LEN_BITS;
	lp->init_block.tx_ring = ((unsigned int)virt_to_bus(&lp->tx_ring) & 0xffffff) | TX_RING_LEN_BITS;
	l = (unsigned int)virt_to_bus(&lp->init_block);


	pcnet32_wio_write_csr(ioaddr, 1, (unsigned short)l);
	pcnet32_wio_write_csr(ioaddr, 2, (unsigned short)(l >> 16) & 0xff);

	print("PCnet32_probe1->set init block: set init block successfully\n");

        /*
         *
         * Bit 11 in CSR4 = APAD_XMT = Auto Pad Transmit. When set, 
         *  Automatic padding is enabled during transmit
         *
         * Bit 8 in CSR4 = Missed Frame Counter Overflow Mask
         *  setting bit 8 disables MFCO from setting INTR in CSR0
         *
         * Bit 4 in CSR4 = Receive Collision Counter-Overflow Mask
         *  setting bit 4 disables RCVCCO from setting INTR in CSR0
         *
         * Bit 2 in CSR4 = Transmit start mask
         *  setting bit 2 disables TXSTRT from setting INTR in CSR0
         *
         * Bit 0 in CSR = Jabber Error mask
         *  setting bit 0 disables JAB bit from setting INTR in CSR0
         *
         */
       	pcnet32_wio_write_csr(ioaddr, 0x4, 0x915);

        /*
         *
         * Bit number 0 of PCnet-32 controller's CSR0 is INIT bit
         *  INIT bit is set to enable the PCnet-32 controller to read
         *  the init block from the memory
         *
         */
	pcnet32_wio_write_csr(ioaddr, 0x0, (unsigned short) 0x1);

	/*
         *
         * Get station address
         *
         */
	print("PCnet32_probe1->get station address\n");

	for (i = 0; i < 6; ++i) {
		lp->node_addr[i] = inb(ioaddr+i);
	}	

	/*
         *
         * enable autoselect
	 *  Turn on auto-select of media (10baseT or BNC) so that the user watch the LEDs
	 *  Don't touch 10base2 power bit
         *
         */
	pcnet32_wio_write_bcr(ioaddr, 0x0002, pcnet32_wio_read_bcr(ioaddr, 0x0002)|0x0002);
	

	return 0;

err_release_region:
	print("PCnet32_probe1->fail  : Failed to do probe on pcnet32\n");
	
	return -1;

}


/**************************************************************************
TRANSMIT - Transmit a frame
***************************************************************************/
void pcnet32_transmit(
	const char *d,			/* Destination */
	unsigned int t,			/* Type */
	unsigned int s,			/* size */
	unsigned char *p)			/* Packet */
{
	struct pcnet_priv *lp = &pcnet32_private;
	unsigned long ioaddr = pcnet_entry_point;
	int time, i;

	print("PCnet32_transmit->start\n");

#if 0
	/* 
	 *
         * if pcnet32 CSR0 start flag is not set, then set start flag 
	 *  check start flag or do adapter reset TBD 
         *
         */	
	pcnet32_wio_write_csr(ioaddr, 0, 0x2);
#endif

	/*
         *
         * copy the packet to ring buffer
         *
         */
	memcpy(lp->tbuf, d, ETH_ALEN);	/* Destination MAC Address */
	memcpy(&lp->tbuf[ETH_ALEN], lp->node_addr, ETH_ALEN); /* Source MAC Address */
	lp->tbuf[ETH_ALEN+ETH_ALEN] = t >> 8;	/* Type bits */
	lp->tbuf[ETH_ALEN+ETH_ALEN+1] = t;	/* Type bits */
	memcpy(&lp->tbuf[ETH_ALEN + ETH_ALEN + 2], p, s); /* packet */
	s += ETH_HLEN;
	
	/*
         *
         * if (chip_table[chip_version].flags & LANCE_MUST_PAD)
	 *		while (s < ETH_ZLEN)
	 *			lp->tbuf[s++] = 0;
         *
         */

	lp->tx_ring[0].buf_length = -s;
	lp->tx_ring[0].misc = 0x0;

	/*
         *
         * OWN, STP, ENP
         *
         */
	lp->tx_ring[0].u.base = (unsigned int)virt_to_bus(lp->tbuf) & 0xffffff;

	/*
         *
         * we set the top byte as the very last thing
         *
         */
	lp->tx_ring[0].u.addr[3] = 0x83;

	/*
         *
         * Trigger an immediate send poll 
	 *  Klaus Espenlaub: the value below was 0x48, but that enabled the
	 *  interrupt line, causing a hang if for some reasone the interrupt
	 *  controller had the LANCE interrupt enabled.  I have no idea why
	 *  nobody ran into this before...  
         *
         */
	pcnet32_wio_write_rap(ioaddr, 0x0); 
	pcnet32_wio_read_rap(ioaddr); 
	pcnet32_wio_write_csr(ioaddr, 0, 0x08);

	/*
         *
         * wait 1 seconf for transmit to complete
         *
         */
	time = 60000;
	i = 0;
	while (i < time && (lp->tx_ring[0].u.base & 0x80000000) != 0)
		i++;
	if ((lp->tx_ring[0].u.base & 0x80000000) != 0){
		print("PCnet32_transmit->timeout: timed out on transmit\n");
		
	}
	

        /*
         *
         * Clear transmit + init done
         *
         */
	pcnet32_wio_read_rap(ioaddr);
	pcnet32_wio_write_csr(ioaddr, 0, 0x200);

	print("PCnet32_transmit->finish\n");
	
	return;
}

/**************************************************************************
RESET - Reset adapter
***************************************************************************/
void pcnet32_reset(unsigned long ioaddr)
{
        struct pcnet_priv *lp = &pcnet32_private;
	int		i;
	unsigned int	l;
		
	print("PCnet32_reset->start\n");
	

	/*
         *
         * Reset the PCnet32
         *
         */
	pcnet32_wio_reset(ioaddr);

	/*
         *
         * ENABLE AUTOSELECT
         *
         */
	pcnet32_wio_write_bcr(ioaddr, 0x0002, pcnet32_wio_read_bcr(ioaddr, 0x0002)|0x0002);
	 
	/*
         *
         * Re-initialise the LANCE, and start it when done
         *
         *  Set station address
         *
         */
	for (i = 0; i < ETH_ALEN; ++i)
		lp->init_block.phy_addr[i] = lp->node_addr[i];
	/*
         *
         * Preset the receive ring headers
         *
         */
	for (i=0; i<RX_RING_SIZE; i++) {
		lp->rx_ring[i].buf_length = -ETH_FRAME_LEN-4;
		/*
                 *
                 * OWN
                 *
                 */
		lp->rx_ring[i].u.base = (unsigned int)(virt_to_bus(lp->rbuf[i])) & 0xffffff;
		/*
                 *
                 * we set the top byte as the very last thing
                 *
                 */
		lp->rx_ring[i].u.addr[3] = 0x80;
	}
	lp->rx_idx = 0;
	lp->init_block.mode = 0x0;	/* enable Rx and Tx */
	l = (unsigned int)virt_to_bus(&lp->init_block);

	/*
         *
         * lp->init_block.filter[0] = lp->init_block.filter[1] = 0x0;
	 * lp->init_block.rx_ring = ((unsigned int)virt_to_bus(&lp->rx_ring) & 0xffffff) | RX_RING_LEN_BITS;
	 * lp->init_block.tx_ring = (int)virt_to_bus(&lp->tx_ring) & 0xffffff;
         *
         */

	pcnet32_wio_write_csr(ioaddr, 1, (unsigned short)l);
	pcnet32_wio_write_csr(ioaddr, 0x2, (unsigned short)(l >> 16));

	/*
         *
         * pcnet32_wio_write_csr(ioaddr, 0x3, 0x5b00);
 	 * pcnet32_wio_write_csr(ioaddr, 0x4, 0x1115);
         *
         */
	pcnet32_wio_write_csr(ioaddr, 0x4, 0x915);
	
	pcnet32_wio_read_csr(ioaddr, 0x0);

	pcnet32_wio_write_csr(ioaddr, 0, 0x4);    /* stop */
	pcnet32_wio_write_csr(ioaddr, 0, 0x1);    /* init */
	for (i = 60000; i > 0; --i)
		if (pcnet32_wio_read_csr(ioaddr, 0x0) & 0x100)
			break;
	if (i <= 0) {
		print("PCnet32_reset->error: init time out\n");
		
	}
	/*
         *
         * Apparently clearing the InitDone bit here triggers a bug
	 *  in the '974. (Mark Stockton) 
         *
         */
	pcnet32_wio_write_csr(ioaddr, 0, 0x2);    /* start */
	
	print("PCnet32_reset->finish\n");

	return;
}
